/*
-------------------------------------------------------------------
BIE is Copyright 2001-2003 Brunswick Corp.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/bie_license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
*/
package com.webdeninteractive.sbie.soap;

import com.webdeninteractive.sbie.ProtocolHandler;
import com.webdeninteractive.sbie.ServiceEndpoint;
import com.webdeninteractive.sbie.Client;
import com.webdeninteractive.sbie.Service;
import com.webdeninteractive.sbie.exception.TransportException;

import java.net.URL;
import java.rmi.RemoteException;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.soap.Name;
import javax.xml.soap.SOAPException;
import javax.xml.rpc.ServiceException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.namespace.QName;

import org.apache.axis.client.Call;
// used but *not* imported, conflicts with c.w.sbie.Service
//import org.apache.axis.client.Service;
import org.apache.axis.encoding.XMLType;
import org.apache.axis.message.SOAPBodyElement;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.message.SOAPBody;
import org.apache.axis.message.MessageElement;
import org.apache.axis.AxisFault;

/**
 * ProtocolHandler for sending SBIE messages via SOAP.
 *
 * @author  mlee
 * @version $Id: SoapProtocolHandler.java,v 1.1.1.1 2003/05/20 16:56:49 gdf Exp $ 
 */
public class SoapProtocolHandler implements ProtocolHandler {

    private static final Logger logger = Logger.getLogger("com.webdeninteractive.sbie.soap.SoapProtocolHandler");
    
    protected Client parent;

    public void setParentClient( Client parent ) {
        this.parent = parent;
    }

    public Client getParentClient() {
        return this.parent;
    }

    public ServiceEndpoint createServiceEndpoint( Service service ) {
        SoapServiceEndpoint ep = new SoapServiceEndpoint();
        // configure with service.getParameter(), if needed
        return ep;
    }

    /** Send a message via the implemented protocol.
     *  It's up to a Service calling this method to package and interpret
     *  the stream data.
     *  @param in Document containing data to send as message.
     *  @return Document containing response message.
     *  @throws TransportException if the message could not be sent.
     */
    public Document sendMessage(ServiceEndpoint endpoint, Document in)
        throws TransportException
    {
        try {
            SoapServiceEndpoint sep = (SoapServiceEndpoint) endpoint;
            URL url = sep.getTargetEndpointAddress();
            String methodName = sep.getMethod();

            Call call = (Call)(new org.apache.axis.client.Service().createCall());
            call.setTargetEndpointAddress( url );
            call.setOperationName( methodName );
            // Create an envelope, and wrap up the document
            SOAPEnvelope req = new SOAPEnvelope();
            /* getAsDOM throws Exception: stupid axis */
            Element reqAsDOM = req.getAsDOM();
            SOAPBody body = (SOAPBody)req.getBody();
            
            /* Now, it'd be nice if we could just add the given document
             * as a new body element, wouldn't it?
             * But body.addChild loses the Call's "operation" */
            /* Apparently, this would be TOO SIMPLE FOR SOAP:
               body.addChild( new SOAPBodyElement(in.getDocumentElement()) );
            */

            /* THESE INTERFACES ARE A CRIME AGAINST HUMANITY */
            Element inRoot = in.getDocumentElement();
            String rootTagName = inRoot.getTagName();
            int colon = rootTagName.indexOf(":");
            String prefix = "bie";
            String localTagName = rootTagName;
            String uri = "http://www.xbotts.com/bie/";
            if (colon >= 0) {
                prefix = rootTagName.substring(0, colon);
                localTagName = rootTagName.substring(colon+1);
                uri = inRoot.getAttribute("xmlns:" + prefix);
            }
            Name name = req.createName( localTagName, prefix, uri );
            SOAPBodyElement msgelt = 
                (SOAPBodyElement)body.addBodyElement(name);
            NamedNodeMap attrs = inRoot.getAttributes();
            if ( attrs!=null ) {
                for( int i=0; i<attrs.getLength(); i++ ) {
                    Attr attr = (Attr)attrs.item(i);
                    String aname = attr.getName();
                    if (aname.startsWith(prefix+":")) {
                        String value = attr.getValue();
                        String localAttrName = 
                            aname.substring( aname.indexOf(":")+1 );
                        Name attrName = 
                            req.createName( localAttrName, prefix, uri );
                        msgelt.addAttribute( attrName, value );
                    }
                }
            }
            // insert all child nodes of input Document
            NodeList nl = in.getDocumentElement().getChildNodes();
            for( int i=0; i<nl.getLength(); i++ ) {
                Node n = nl.item(i);
                if ( n.getNodeType() == Node.ELEMENT_NODE ) {
                    msgelt.addChildElement( new MessageElement((Element)n) );
                }
            }

            // send the SOAP request:
            if ( sep.getUser() != null ) {
                call.setUsername( sep.getUser() );
            }
            if ( sep.getPassword() != null ) {
                call.setPassword( sep.getPassword() );
            }

            //logger.debug("XXX soapenv:\n" + req.toString() );

            SOAPEnvelope resp = call.invoke( req ); // throws AxisFault

            // unwrap request, return the document
            return unwrapResponse(resp);

        } catch (AxisFault e) {
            // AxisFaults are REALLY long-winded
            logger.debug( "Could not send message", e );
            logger.info( "SOAP Call error: " + e.getFaultString() );
            throw new TransportException("Could not send message (failed to invoke Call)");

        } catch (Exception e) { // Ugh
            /* ServiceException: if a new SOAP Service cannot 
             *          be created (axis doesn't document why)
             * SOAPException: if the errors occur in creating the SOAP message
             * RemoteException: if the message cannot be sent
             */
            throw new TransportException("Could not send message", e);
        }
    }

    public static Element documentToElement( Document sourceDoc,
                                             Document newOwner )
    {
        Element newElement = (Element)newOwner.importNode( 
            sourceDoc.getDocumentElement(), true );
        return newElement;
    }

    public static Document unwrapResponse( SOAPEnvelope resp )
        throws Exception
    {
        Element msgElement = resp.getFirstBody().getAsDOM();
        // create new doc, contents of which are the msgElement
        DocumentBuilderFactory builderfactory = 
            DocumentBuilderFactory.newInstance();
        builderfactory.setValidating(false);
        DocumentBuilder builder = builderfactory.newDocumentBuilder();
        Document msgDoc = builder.newDocument();
        msgDoc.appendChild( msgDoc.importNode( msgElement, true ) );
        return msgDoc;
    }
        
}
